#include "nes.h"

#include <string.h>
#include <Windows.h>

#include "misc_func.h"

CNes* g_NES = 0;

CNes::CNes(DrawFrame draw)
{
  memset(this, 0, sizeof(*this));
  m_FuncDrawFrame = draw;
  m_StopEmu = true;
  m_Cpu = new CCpu(this);
  m_Ppu = new CPpu(this);
  m_JoyPad = new CJoyPad();
  m_VideoMemory = new CVideoMemory(this);
}

CNes::~CNes()
{
  delete m_VideoMemory;
  delete m_JoyPad;
  delete m_Ppu;
  delete m_Cpu;
}

void
CNes::Reset()
{
  m_Cpu->Reset();
  m_Ppu->Reset();
  m_JoyPad->Reset();
  m_VideoMemory->Reset();
  m_StopEmu = true;
}

u8
CNes::ReadByte(u16 addr)
{
  return m_Cpu->ReadByte(addr);
}

u16
CNes::ReadWord(u16 addr)
{
  return m_Cpu->ReadWord(addr);
}

void
CNes::Write(u16 addr, u8 data)
{
  m_Cpu->Write(addr, data);
}

u8
CNes::ReadPort(u16 port)
{
  u8 value= 0;
  switch (port)
  {
    case 0x2002:
      m_Ppu->m_IsLowAddress = false;
      value = m_Ppu->Register[2];
      m_Ppu->Register[2] &= ~PPU_VBLANK_FLAG;
      break;

    case 0x2004:
      value = m_Ppu->ReadSprRam();
      break;

    case 0x2007:
      value = m_Ppu->ReadVRam();
      break;

    case 0x4016:
    case 0x4017:
      value = m_JoyPad->ReadState(port-0x4016);
      break;
  }
  return value;
}

void
CNes::WritePort(u16 port, u8 data)
{
  switch (port)
  {
  case 0x2000:
    m_Ppu->Register[0] = data;
    break;
  case 0x2001:
    m_Ppu->Register[1] = data;
    break;
  case 0x2002:
    m_Ppu->Register[2] = data;
    break;
  case 0x2003:
    m_Ppu->SetSprAddr(data);
    break;
  case 0x2004:
    m_Ppu->WriteSprRam(data);
    break;
  case 0x2005:
    m_Ppu->Scroll(data);
    break;
  case 0x2006:
    m_Ppu->SetAddress(data);
    break;
  case 0x2007:
    m_Ppu->WriteVRam(data);
    break;
  case 0x4014:
    DMA(data);
    break;
  case 0x4016:
  case 0x4017:
    m_JoyPad->WriteState(port-0x4016, data);
    break;
  }
}

void
CNes::DMA(u16 data)
{
  u16 addr = data << 8;
  u8* buf = m_Cpu->GetBuf(addr);
  if (buf != 0) {
    m_Ppu->SprDMA(buf);
  }
  m_Cpu->m_ExtraCycle = 512;
}

void
CNes::Step()
{
  m_Cpu->Execute(1);
}

void
CNes::Run()
{
  int cycles    = 0;
  m_Scanlines = 0;
  m_StopEmu = false;
  f64 FramePeriod = 1000.0 / FRAME_RATE;
  while (!m_StopEmu) {
    u32 start = timeGetTime();

    for (int i = 0; i < SCANLINES_VBLANK_END; i++) {
      m_Cpu->Execute(CYCLES_PER_SCANLINE);
      HSync();
    }

    int rest_time = (int)FramePeriod - (timeGetTime() - start);
    if (rest_time < 0) {
      rest_time = 0;
    }
    ::Sleep(rest_time);
  }
}

void
CNes::HSync()
{
  // Draw a scanline
  if (m_Scanlines < SCANLINES_VBLANK_START) {
    m_VideoMemory->DrawScanLine(m_Scanlines);
  }

  // Scan Line 0
  if (m_Scanlines == FRAME_START) {
    m_Ppu->FrameStart();
  }
  // Scan Line 240
  if (m_Scanlines == FRAME_END) {
    m_FuncDrawFrame();
  }

  // Scan Line 243, Vblank start now
  if (m_Scanlines == SCANLINES_VBLANK_START) {
    m_Ppu->VBlankStart();
  } else if (m_Scanlines == SCANLINES_VBLANK_END) {
    m_Scanlines = 0;
    m_Ppu->VBlankEnd();
    return;
  }
  m_Scanlines++;
}

void
CNes::Stop()
{
  m_StopEmu = true;
  m_Ppu->Stop();
  m_Cpu->Stop();
  m_VideoMemory->ClearScreen();
}

bool
CNes::Running()
{
  return !m_StopEmu;
}

void
CNes::NMIReq()
{
  m_Cpu->NMIReq();
}

static void
ReadRom(FILE* fp, u8* buf, int page_count, int page_size)
{
  if (!page_count) {
    return;
  }

  int size  = page_count * page_size;
  int s_read= 0;
  while (size > 0) {
    u8* tmp = buf + s_read;
    s_read += fread(tmp, 1, size, fp);
    size -= s_read;
  }
}
bool
CNes::LoadRom(wchar_t* file)
{
  FILE* fp= _wfopen(file, L"rb");
  if (fp == 0) {
    return false;
  }
  fread(&m_Header, sizeof(m_Header), 1, fp);
  if (m_Header.control1 & TrainerExist) {
    fseek(fp, 512, SEEK_CUR);
  }

  m_MapperNo = m_Header.control1 >> MAPPER_NO;
  if (m_MapperNo != 0 && m_MapperNo != 2) {
    fclose(fp);
    return false;
  }
  m_Ppu->SetNameTblBank(m_Header.control1 & VMirror);
  m_Cpu->InitRom(m_MapperNo, m_Header.prg_count);
  ReadRom(fp, m_Cpu->ROM, m_Header.prg_count, PRGPageSize);
  ReadRom(fp, m_Ppu->PatternTable[0], m_Header.chr_count, CHRPageSize);
  fclose(fp);
  return true;
}

u8*
CNes::GetPatternTbl(int index)
{
  return m_Ppu->PatternTable[index];
}

u8*
CNes::GetNameTbl(int index)
{
  return m_Ppu->NameTable[index];
}

u8*
CNes::GetAttribTbl(int index)
{
  return m_Ppu->AttribTable[index];
}

u16*
CNes::GetPatternImage()
{
  m_VideoMemory->BuildPatternTbl();
  return m_VideoMemory->m_PatternImage;
}

u16*
CNes::GetNameTblImage()
{
  m_VideoMemory->BuildNameTbl();
  return m_VideoMemory->m_NameTblImage;
}

u16*
CNes::GetSpriteImage()
{
  m_VideoMemory->BuildSprites();
  return m_VideoMemory->m_SpriteImage;
}

u16*
CNes::GetFrame()
{
  m_VideoMemory->BuildFrame();
  return m_VideoMemory->m_FrameBuf;
}

u16*
CNes::GetFinalFrame()
{
  return m_VideoMemory->m_FinalFrameBuf;
}

u8*
CNes::GetBGPalette()
{
  return m_Ppu->BGPalettes;
}

u8*
CNes::GetSPPalette()
{
  return m_Ppu->SPPalettes;
}

void
CNes::SetKeyState(int index, u8 key, u8 down)
{
  m_JoyPad->SetKeyState(index, key, down);
}
